﻿#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// These methods © 2013 Jon Hanna. Contributed to the above project.
// Licenses under the Apache License, Version 2.0 as per above.
#endregion
namespace MoreLinq
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    public static partial class MoreEnumerable
    {
        private class CallEnumerator<T> : IEnumerator<T>
        {
            private Func<T> _factory;
            private T _current;
            private int _count;
            private readonly bool _callOnce;
            public CallEnumerator(Func<T> factory, int count, bool callOnce)
            {
                _factory = factory;
                _count = count;
                _callOnce = callOnce;
            }
            public T Current
            {
                get { return _current; }
            }
            object IEnumerator.Current
            {
                get { return Current; }
            }
            void IDisposable.Dispose()
            {
            }
            public bool MoveNext()
            {
                if(_count == 0)
                    return false;
                if(_factory != null)
                {
                    _current = _factory();
                    if(_callOnce)
                        _factory = null;
                }
                if(_count != -1)
                    --_count;
                return true;
            }
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
        }
        private class CallEnumerable<T> : IEnumerable<T>
        {
            private readonly Func<T> _factory;
            private readonly int _count;
            private readonly bool _callOnce;
            public CallEnumerable(Func<T> factory, int count, bool callOnce)
            {
                _factory = factory;
                _count = count;
                _callOnce = callOnce;
            }
            public IEnumerator<T> GetEnumerator()
            {
                return new CallEnumerator<T>(_factory, _count, _callOnce);
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
        /// <summary>
        /// Produces an <see cref="IEnumerable{T}"/> by calling a <see cref="Func{TResult}"/> to
        /// create it's items.
        /// </summary>
        /// <param name="factory">The <see cref="Func{TResult}"/> to call.</param>
        /// <param name="repeat">The number of times to repeat the sequence.</param>
        /// <param name="callOnce">True to call the factory only once and repeat the same result.
        /// False to call it each time (default is false).</param>
        /// <returns>The <see cref="IEnumerable{T}"/> of the sequence.</returns>
        public static IEnumerable<T> RepeatCall<T>(this Func<T> factory, int repeat, bool callOnce)
        {
            return new CallEnumerable<T>(factory, repeat, callOnce);
        }
        /// <summary>
        /// Produces an <see cref="IEnumerable{T}"/> by calling a <see cref="Func{TResult}"/> to
        /// create it's items.
        /// </summary>
        /// <param name="factory">The <see cref="Func{TResult}"/> to call.</param>
        /// <param name="repeat">The number of times to repeat the sequence.</param>
        /// <returns>The <see cref="IEnumerable{T}"/> of the sequence.</returns>
        public static IEnumerable<T> RepeatCall<T>(this Func<T> factory, int repeat)
        {
            return factory.RepeatCall(repeat, false);
        }
        /// <summary>
        /// Produces an <see cref="IEnumerable{T}"/> by calling a <see cref="Func{TResult}"/> indefinitely to
        /// create it's items.
        /// </summary>
        /// <param name="factory">The <see cref="Func{TResult}"/> to call.</param>
        /// <param name="callOnce">True to call the factory only once and repeat the same result.
        /// False to call it each time (default is false).</param>
        /// <returns>The <see cref="IEnumerable{T}"/> of the sequence.</returns>
        public static IEnumerable<T> RepeatCall<T>(this Func<T> factory, bool callOnce)
        {
            return new CallEnumerable<T>(factory, -1, callOnce);
        }
        /// <summary>
        /// Produces an <see cref="IEnumerable{T}"/> by calling a <see cref="Func{TResult}"/> indefinitely to
        /// create it's items.
        /// </summary>
        /// <param name="factory">The <see cref="Func{TResult}"/> to call.</param>
        /// <returns>The <see cref="IEnumerable{T}"/> of the sequence.</returns>
        public static IEnumerable<T> RepeatCall<T>(this Func<T> factory)
        {
            return new CallEnumerable<T>(factory, -1, false);
        }
        /// <summary>
        /// Produces a single-item <see cref="IEnumerable{T}"/> by calling a <see cref="Func{TResult}"/>
        /// to produce the item.
        /// </summary>
        /// <param name="factory">The <see cref="Func{TResult}"/> to call.</param>
        /// <returns>The <see cref="IEnumerable{T}"/> of the single-item sequence.</returns>
        public static IEnumerable<T> EmitCall<T>(this Func<T> factory)
        {
            return factory.RepeatCall(1, true);
        }
    }
}
